//////////////////////////////////////////////////////////////////////////
/// @file QAFTrace.cpp
/// @brief Set of classes for printing debug logs to files.
//////////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include "qaftrace.h"

/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!
#ifndef DISABLE_Q_TRACE
/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!

#ifdef WIN32
	#include <crtdbg.h>
#endif

#include <malloc.h>
#include <stdarg.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>

// Windows-specific code
#ifdef WIN32

	#include <io.h>
	#include <direct.h>
	#include <shlobj.h>

	// In order to link to version.lib automatically
	#pragma message("Automatic link to version.lib")
	#pragma comment(lib, "version.lib")

	const int R_OK = 0;
	const int W_OK = 0;

	const Q_TCHAR PATH_DELIMITER = _T('\\');
	const Q_TCHAR PATH_DELIMITER_STR[] = _T("\\");

	#define EOL "\r\n"
	#define TEOL _T(EOL)

// Linux-specific code
#else

	#include <sys/time.h>
	#include <errno.h>
	#include <unistd.h>

	const Q_TCHAR PATH_DELIMITER = _T('/');

	#define EOL "\n"
	#define TEOL _T(EOL)

	#define _tfopen fopen
	#define _stat stat
	#define _tstat stat
	#define _tremove remove
	#define _trename rename

#endif

/// Beginning of the format string >>>
/// 12:31 15:05:49:547 [A5F:E67] Trace message <filename.cpp:120>
const char TRACE_FORMAT_PRE_STRING[] = "%02d:%02d %02d:%02d:%02d:%03d [%X:%X] ";
const int TRACE_FORMAT_PRE_STRING_LEN = 42; // in characters + some reserve space

/// Beginning of the format string >>>
// 15:05:49:547 [A5F:E67] Memory dump of "CType", address 0x120A, size 16b (0x10) <filename.cpp:120>
const char TRACE_FORMAT_DUMP_STRING[] = "Memory dump of \"%s\", address 0x%X, size %db (0x%X)";
const int TRACE_FORMAT_DUMP_STRING_LEN = Q_MAX_PATH; // in characters + some reserve space

/// End of the format string with file name and line
const Q_TCHAR TRACE_FORMAT_POST_STRING[] = _T(" <%s:%d>");

/// Timeout to wait for the trace device to be unlocked - default value in msec.
const unsigned long DEFAULT_TIMEOUT = 500;

/// Key under HKLM where all error logs are configured
const Q_TCHAR LOG_REGISTRY_KEY[] = _T("Software\\") _T(QAFDEBUG_COMPANY_NAME) _T("\\Log\\");


/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Device class
/////////////////////////////////////////////////////////////////////////////////////////////

/// Set new device and return the record
/*inline QTrace::Record & QTrace::Device::operator <<( QTrace::Record & record )
{
	record.SetDevice( this );
	return record;
}*/

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Record class
/////////////////////////////////////////////////////////////////////////////////////////////

/// Output the current trace record to the trace device
void QTrace::Record::DoTrace()
{
	if( Q_ASSERT( NULL != m_pDevice ) )
	{
		m_pDevice->DoTrace( *this );
		m_bTraced = true;
	}
}

/// Write count of bytes to the trace device (wrapper for the Device method)
bool QTrace::Record::Write( const void * const pBuffer,
	const unsigned long dwBufferSize, unsigned long & dwBytesWrittenRet )
{
	if( Q_ASSERT( NULL != m_pDevice ) )
		return m_pDevice->Write( pBuffer, dwBufferSize, dwBytesWrittenRet );
	else
		return false;
}

/// This function will be called from the device's DoTrace() function
/// The device opened the output stream already, and this function
/// can write the record to it.
unsigned long QTrace::Record::TraceRecord( const TraceStage stage, bool & bFinishedRet )
{
	unsigned long dwWritten = 0;
	if( TRACE_HEADER == stage )
	{
		// str() calls freeze(true), we must unfreeze it back to prevent a memory leak
		Q_ASSERT( Write( m_Buffer.str().c_str(), m_Buffer.str().length(), dwWritten ) );
		//#ifndef WIN32
		//	m_Buffer.freeze( false );
		//#endif
		Q_ASSERT( dwWritten == m_Buffer.str().length() );
		bFinishedRet = true;
	}
	return dwWritten;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// QAFTrace::Log class
/////////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Dump class
/////////////////////////////////////////////////////////////////////////////////////////////

/// This function does the actual work of writting to the given log
unsigned long QTrace::Dump::TraceRecord( const TraceStage stage, bool & bFinishedRet )
{
	unsigned long dwWritten = 0;
	if( TRACE_HEADER == stage )
	{
		// prepare the dump message string
		char szDumpMessage[TRACE_FORMAT_DUMP_STRING_LEN] = { 0 };
		#ifdef _UNICODE
			CQWideCharToMultiByte conv( m_szTypeName, CP_ACP );
			#if defined(WIN32) && (_MSC_VER >= 1400)
				sprintf_s( szDumpMessage, TRACE_FORMAT_DUMP_STRING_LEN, TRACE_FORMAT_DUMP_STRING, 
					conv.buffer(), m_pBuf, m_ulBufSize, m_ulBufSize  );
			#else
				sprintf( szDumpMessage, TRACE_FORMAT_DUMP_STRING, conv.buffer(),
					m_pBuf, m_ulBufSize, m_ulBufSize  );
			#endif
		#else
			#if defined(WIN32) && (_MSC_VER >= 1400)
				_stprintf_s( szDumpMessage, TRACE_FORMAT_DUMP_STRING_LEN, TRACE_FORMAT_DUMP_STRING, 
					m_szTypeName, m_pBuf, m_ulBufSize, m_ulBufSize );
			#else
				_stprintf( szDumpMessage, TRACE_FORMAT_DUMP_STRING, m_szTypeName, 
					m_pBuf, m_ulBufSize, m_ulBufSize );
			#endif
		#endif
		// write the complete trace message
		Write( szDumpMessage, strlen(szDumpMessage), dwWritten );
		bFinishedRet = false;
	}
	if( TRACE_BODY == stage )
	{
		// write the buffer to the file
		const unsigned short BUF_LEN = 20;
		char szDumpMessage[BUF_LEN] = { 0 };
		const unsigned char * pPos = (const unsigned char *) m_pBuf;
		const unsigned char * pEnd = pPos + m_ulBufSize;
		while( pPos < pEnd )
		{
			szDumpMessage[0] = 0;
			#if defined(WIN32) && (_MSC_VER >= 1400)
				sprintf_s( szDumpMessage, BUF_LEN, "%08X  ", pPos );
			#else
				sprintf( szDumpMessage, "%08X  ", pPos );
			#endif
			unsigned long dwBytes = 0;
			Write( szDumpMessage, strlen(szDumpMessage), dwBytes );
			dwWritten += dwBytes;
			for( int i = 0; i < 16; i++ )
			{
				if( (pPos + i) < pEnd )
				{
					szDumpMessage[0] = 0;
					unsigned char uc = ((unsigned char *)pPos)[i];
					#if defined(WIN32) && (_MSC_VER >= 1400)
						sprintf_s( szDumpMessage, 4, "%02X ", uc );
					#else
						sprintf( szDumpMessage, "%02X ", uc );
					#endif
					Write( szDumpMessage, 3, dwBytes );
					dwWritten += dwBytes;
				}
				else
				{
					Write( "   ", 3, dwBytes );
					dwWritten += dwBytes;
				}
				if( 7 == i )
				{
					Write( " ", 1, dwBytes );
					dwWritten += dwBytes;
				}
			}
			Write( " ", 1, dwBytes );
			dwWritten += dwBytes;
			for( int j = 0; j < 16; j++ )
			{
				if( (pPos + j) >= pEnd )
					break;
				if( (pPos[j] > 0x20) && (pPos[j] < 0x80) )
					Write( pPos + j, 1, dwBytes );
				else
					Write( ".", 1, dwBytes );
				dwWritten += dwBytes;
			}
			Write( EOL, 2, dwBytes );
			dwWritten += dwBytes;
			pPos += 16;
		}
		bFinishedRet = true;
	}
	return dwWritten;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// Helper formatting classes
/////////////////////////////////////////////////////////////////////////////////////////////

/// This is a printf-style formatting class. Use it with causion!
/// printf-style functions cannot detect ASCII and UNICODE strings and might fail!
/// This is also a base class for other formatting helper classes.
/// Be careful with this class since it cannot detect ASCII and UNICODE strings.
/// It will assume it works with strings of TCHAR.
QTrace::Format::Format( Q_LPCTSTR szFormat, ... ) : m_szBuffer(NULL), m_szConverted(NULL)
{
	try
	{
		int nBufSize = Q_MAX_PATH;
		va_list args;
		while( NULL == m_szBuffer )
		{
			m_szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * nBufSize );
			// try to print the string to the buffer
			va_start( args, szFormat );
			#ifndef _vsntprintf
				#define _vsntprintf vsnprintf
			#endif
			#if defined(WIN32) && (_MSC_VER >= 1400)
				int iRet = _vsntprintf_s( m_szBuffer, nBufSize, nBufSize - 1, szFormat, args );
			#else
				int iRet = _vsntprintf( m_szBuffer, nBufSize - 1, szFormat, args );
			#endif
			va_end( args );
			// if the buffer is too small, make its size twice larger and repeat
			if( iRet < 0 )
			{
				free( m_szBuffer );
				m_szBuffer = NULL;
				nBufSize *= 2;
			}
		}
	}
	catch(...)
	{
		if( NULL != m_szBuffer )
		{
			free( m_szBuffer );
			m_szBuffer = NULL;
		}
	}
}

QTrace::Format::~Format()
{
	if( NULL != m_szConverted )
	{
		free( m_szConverted );
		m_szConverted = NULL;
	}
	if( NULL != m_szBuffer )
	{
		free( m_szBuffer );
		m_szBuffer = NULL;
	}
}

Q_LPCSTR QTrace::Format::GetTextA()
{
	if( Q_INVALID( NULL == m_szBuffer ) )
		return NULL;
#ifdef _UNICODE
	QTrace::CQWideCharToMultiByte conv( m_szBuffer, CP_ACP );
	m_szConverted = const_cast<Q_LPSTR>(conv.Detach());
	return m_szConverted;
#else
	return m_szBuffer;
#endif
}

#ifdef WIN32
Q_LPCWSTR QTrace::Format::GetTextW()
{
	if( Q_INVALID( NULL == m_szBuffer ) )
		return NULL;
#ifdef _UNICODE
	return m_szBuffer;
#else
	return NULL;
#endif
}
#endif


/// Linux and Windows class for getting the current time up to msec
class CSysTime
{
public:

	/// Stores msec value of the last call to Now() or the class instance creation.
	unsigned int msec;

	/// Stores sec value of the last call to Now() or the class instance creation.
	unsigned int sec;

	/// Stores min value of the last call to Now() or the class instance creation.
	unsigned int min;

	/// Stores hour value of the last call to Now() or the class instance creation.
	unsigned int hour;

	/// Stores day value of the last call to Now() or the class instance creation.
	unsigned int day;

	/// Stores month value of the last call to Now() or the class instance creation.
	unsigned int month;

	/// Stores year value of the last call to Now() or the class instance creation.
	unsigned int year;

	/// Default constructor. It calculates and stores the time of the class instance creation.
	CSysTime()
	{
		Now();
	}

	/// This function calculates and stores the current time.
	void Now()
	{
		#ifdef WIN32 /// Windows-specific implementation
			SYSTEMTIME st;
			GetLocalTime( &st );
			msec = st.wMilliseconds;
			sec = st.wSecond;
			min = st.wMinute;
			hour = st.wHour;
			day = st.wDay;
			month = st.wMonth;
			year = st.wYear;
		#else /// Linux-specific implementation
			struct timeval tp; // time with msec
			time_t tmt;        // time for broking
			struct tm tb;      // broken time
			gettimeofday( &tp, NULL );
			tmt = time( NULL );
			localtime_r( &tmt, &tb );
			msec = tp.tv_usec / 1000; // from microseconds to msec
			sec = tb.tm_sec;
			min = tb.tm_min;
			hour = tb.tm_hour;
			day = tb.tm_mday;
			month = tb.tm_mon;
			year = tb.tm_year + 1900; // They should count from 1970 but use 1900 instead
		#endif
	}

protected:
private:

	/// Copy constructor - disabled
	CSysTime( const CSysTime & obj )
	{
		assert( false );
	}

	/// Assignment operator - disabled
	CSysTime & operator=( const CSysTime & obj )
	{
		assert( false );
		return *this;
	}
};

/// Portable function that returns process id
inline unsigned long get_process_id()
{
	#ifdef WIN32
		return GetCurrentProcessId();
	#else
		return getpid();
	#endif
}

/// Portable function that returns process id
inline unsigned long get_thread_id()
{
	#ifdef WIN32
		return GetCurrentThreadId();
	#else
		return 0;
	#endif
}

/// Helper class for rich formatting of integers
/// Since this class is only needed for rich formatting,
/// you must specify the uiFixedLength. By default, the value
/// will be right-aligned and prevailed with spaces.
QTrace::Num::Num( const signed long lValue, const unsigned int uiFixedWidth,
	const Q_TCHAR bPadChar /*= ' '*/, const bool bLeftAlign /*= false*/ )
{
	const unsigned short BUF_LEN = 12;
	m_szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (uiFixedWidth + BUF_LEN) );
	if( Q_INVALID( NULL == m_szBuffer ) )
		return;
	m_szBuffer[0] = 0;
	Q_TCHAR szTemp[BUF_LEN] = { 0 };
	#if defined(WIN32) && (_MSC_VER >= 1400)
		int iLen = _stprintf_s( szTemp, BUF_LEN, _T("%d"), lValue );
	#else
		int iLen = _stprintf( szTemp, _T("%d"), lValue );
	#endif
	if( iLen < (signed int)uiFixedWidth )
	{
		if( bLeftAlign )
		{
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[iLen + i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
		}
		else
		{
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
		}
	}
	else
		_tcscpy_s( m_szBuffer,uiFixedWidth + BUF_LEN, szTemp );
}

/// Helper class for rich formatting of integers in hex format
/// By default, the hex value will not be padded with zeros,
/// but you may change it. Align has no meaning here since
QTrace::Hex::Hex( const unsigned long ulValue, const unsigned int uiFixedWidth /*= 0*/,
	const Q_TCHAR bPadChar /*= '0'*/, const bool bLeftAlign /*= false*/ )
{
	Convert( ulValue, uiFixedWidth, bPadChar, bLeftAlign );
}

/// By default, the hex value will not be padded with zeros,
/// but you may change it. Align has no meaning here since
QTrace::Hex::Hex( const void * ptr, const unsigned int uiFixedWidth /*= 0*/,
	const Q_TCHAR bPadChar /*= '0'*/, const bool bLeftAlign /*= false*/ )
{
	Convert( reinterpret_cast<const unsigned long>(ptr), uiFixedWidth, bPadChar, bLeftAlign );
}

/// By default, the hex value will not be padded with zeros,
/// but you may change it. Align has no meaning here since
void QTrace::Hex::Convert( const unsigned long ulValue, const unsigned int uiFixedWidth,
	const Q_TCHAR bPadChar, const bool bLeftAlign )
{
	const unsigned short BUF_LEN = 12;
	m_szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (uiFixedWidth + BUF_LEN) );
	if( Q_INVALID( NULL == m_szBuffer ) )
		return;
	m_szBuffer[0] = 0;
	Q_TCHAR szTemp[BUF_LEN] = { 0 };
	#if defined(WIN32) && (_MSC_VER >= 1400)
		int iLen = _stprintf_s( szTemp, BUF_LEN, _T("%X"), ulValue );
	#else
		int iLen = _stprintf( szTemp, _T("%X"), ulValue );
	#endif
	if( iLen < (signed int)uiFixedWidth )
	{
		if( bLeftAlign )
		{
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[iLen + i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
		}
		else
		{
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
		}
	}
	else
		_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
}

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Device class
/////////////////////////////////////////////////////////////////////////////////////////////

QTrace::Device::Device( Q_LPCTSTR szID, const bool bNeedMutex,
	const QTrace::TraceLevel nDefaultTraceLevel )
	: m_hMutex(NULL), m_szID(NULL), m_nDefaultTraceLevel(nDefaultTraceLevel),
	m_dwWaitTimeout(DEFAULT_TIMEOUT), m_dwEnabledTraceLevel( QTrace::TraceNone )
{
	/// cannot continue without the correct ID
	if( Q_INVALID( NULL == szID ) && Q_INVALID( 0 == *szID ) )
		return;
	int iLen = _tcslen(szID);
	m_szID = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (iLen + 1) );
	if( Q_INVALID( NULL == m_szID ) )
		return;
	_tcscpy_s( m_szID, iLen + 1, szID );
	/// Create the mutex if needed
	if( bNeedMutex )
	{
		#ifdef WIN32
		Q_LPTSTR szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (iLen + 10) ); // for additional suffix
		if( Q_ASSERT( NULL != szBuffer ) )
		{
			szBuffer[0] = 0;
			_tcscpy_s( szBuffer, iLen + 10, szID );
			_tcscat_s( szBuffer, iLen + 10, _T("_Mtx123") );
			m_hMutex = CreateMutex( NULL, FALSE, szBuffer );
			Q_ASSERT( NULL != m_hMutex );
			free( szBuffer );
		}
		#endif
	}
	// initialize the trace log control registry key
	#ifdef WIN32
		CRegDWORD dwRead( HKLM, LOG_REGISTRY_KEY, szID, TraceNone, REG::RF_READONLY );
		m_dwEnabledTraceLevel = dwRead;
	#else
		// Replace dots with underscores in order to get the environment variable
		Q_TCHAR * szBuf = (Q_TCHAR *)malloc( sizeof(Q_TCHAR) * (iLen + 1) );
		if( NULL != szBuf )
		{
			_tcscpy( szBuf, szID );
			for( char * pch = szBuf; *pch != 0; pch++ )
			{
				if( !isalnum( *pch ) )
					*pch = '_';
			}
			m_dwEnabledTraceLevel = 0;
			char * szVal = getenv( szBuf );
			if( NULL != szVal )
				m_dwEnabledTraceLevel = atoi( szVal );
			free( szBuf ); 
		}
	#endif
}

QTrace::Device::~Device()
{
	if( NULL != m_szID )
	{
		free( m_szID );
		m_szID = NULL;
	}
	if( NULL != m_hMutex )
	{
		#ifdef WIN32
		ReleaseMutex( m_hMutex );
		CloseHandle( m_hMutex );
		#endif
		m_hMutex = NULL;
	}
}

//
void QTrace::Device::DoTrace( QTrace::Record & obj )
{
	/// Protect the device object from cross-thread usage
	/// It is not connected to the mutex since the mutex is not always requested
	CAutoLockCS sync( m_cs );
	// check that the record is supposed to be written
	if( obj.IsEmpty() )
		return;
	// lock the device
	if( ! Lock() )
		return;
	// open the device
	if( ! OpenTraceDevice() )
	{
		Unlock();
		return;
	}
	// write the trace record with the prefix and postfix
	unsigned long dwBytesWritten = WritePrefix( obj, 0 );
	bool bFinished = false;
	dwBytesWritten += obj.TraceRecord( QTrace::Record::TRACE_HEADER, bFinished );
	WritePostfix( obj, dwBytesWritten );
	if( ! bFinished )
		obj.TraceRecord( QTrace::Record::TRACE_BODY, bFinished );
	// close everything
	CloseTraceDevice();
	Unlock();
}

///
bool QTrace::Device::Lock()
{
	#ifdef WIN32
	if( NULL == m_hMutex )
		return false;
	unsigned long dwRet = WaitForSingleObject( m_hMutex, m_dwWaitTimeout );
	return Q_CHECK( WAIT_OBJECT_0, dwRet );
	#else
	return true;
	#endif
}

///
void QTrace::Device::Unlock()
{
	#ifdef WIN32
	if( NULL != m_hMutex )
		Q_ASSERT( ReleaseMutex( m_hMutex ) );
	#endif
}

///
unsigned long QTrace::Device::WritePrefix( Record & record, const unsigned long dwBytesWritten )
{
	// Prepare parameters for the complete error message
	CSysTime st;
	unsigned long dwProcess = get_process_id(), dwThread = get_thread_id();
	// format the string
	char szPreString[TRACE_FORMAT_PRE_STRING_LEN] = { 0 };
	#if defined(WIN32) && (_MSC_VER >= 1400)
		sprintf_s( szPreString, TRACE_FORMAT_PRE_STRING_LEN, TRACE_FORMAT_PRE_STRING, 
			st.month, st.day, st.hour, st.min, st.sec, st.msec, dwProcess, dwThread );
	#else
		sprintf( szPreString, TRACE_FORMAT_PRE_STRING, st.month, st.day, st.hour,
			st.min, st.sec, st.msec, dwProcess, dwThread );
	#endif
	// write the beginning of the trace message
	unsigned long dwWritten = 0;
	Write( szPreString, strlen(szPreString), dwWritten );
	return dwWritten;
}

///
unsigned long QTrace::Device::WritePostfix( Record & record, const unsigned long dwBytesWritten )
{
	unsigned long dwRet = 0;
	unsigned long dwBytes = 0;
	if( NULL != record.GetLocationFile() )
	{
		const char * szFileName = record.GetLocationFile();
		const unsigned short BUF_LEN = 16;
		char szTemp[BUF_LEN] = { 0 }; // for all games with cars
		// write iSpaces space characters (if iSpaces > 0)
		int nSpaces = (unsigned long)GetFarRightColumn() - dwBytesWritten;
		for( int i = 0; i < nSpaces; )
		{
			int iNum = nSpaces - i;
			iNum = iNum < 15 ? iNum : 15;
			memset( szTemp, ' ', iNum );
			szTemp[iNum] = 0;
			Write( szTemp, iNum, dwBytes );
			dwRet += dwBytes;
			i += iNum;
		}
		// write the delimiter, filename, line number and the end of the line
		Write( " <== ", 5, dwBytes );
		dwRet += dwBytes;
		Write( szFileName, strlen(szFileName), dwBytes );
		dwRet += dwBytes;
		#if defined(WIN32) && (_MSC_VER >= 1400)
			sprintf_s( szTemp, BUF_LEN, "(%d)" EOL, record.GetLocationLine() );
		#else
			sprintf( szTemp, "(%d)" EOL, record.GetLocationLine() );
		#endif
		Write( szTemp, strlen(szTemp), dwBytes );
		dwRet += dwBytes;
	}
	else
	{
		Write( EOL, 2, dwBytes );
		dwRet += dwBytes;
	}
	return dwRet;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::LogFile class
/////////////////////////////////////////////////////////////////////////////////////////////

QTrace::LogFile::LogFile( Q_LPCTSTR szFileName, const QTrace::TraceLevel nDefaultTraceLevel,
	const int nMaxLogFileSize )
	: QTrace::Device( szFileName, true, nDefaultTraceLevel ), m_hFile(NULL),
	m_nMaxLogFileSize( nMaxLogFileSize )
{
	// the file name must be specified
	if( Q_INVALID( NULL == szFileName ) || Q_INVALID( 0 == *szFileName ) )
		return;
	m_szFilename[0] = 0;
	if( QAFDebug::GetLogDir( m_szFilename, Q_MAX_PATH - 1 ) > 0 )
	{
		Q_LPCTSTR lpszPos = _tcsrchr( szFileName, PATH_DELIMITER );
		if( NULL != lpszPos )
		{
			// I need to create subdirs here
		}
		else
			_tcscat_s( m_szFilename, Q_MAX_PATH, szFileName );
	}
	// Get the log file size from the registry key.
	// Use "<filename>.size" as the value name.
	const unsigned long MIN_LOG_SIZE = 1024;               // 1 Kb
	const unsigned long DEFAULT_LOG_SIZE = 1024 * 1024;    // 1 Mb
	const unsigned long MAX_LOG_SIZE = 1024 * 1024 * 1024; // 1 Gb
	#define MAKE_LOG_SIZE( x ) ((x) < MIN_LOG_SIZE ? MIN_LOG_SIZE : ((x) > MAX_LOG_SIZE ? MAX_LOG_SIZE : (x)))
	if( _tcslen( szFileName ) < (Q_MAX_PATH - 6) )
	{
		Q_TCHAR szTemp[Q_MAX_PATH] = { 0 };
		_tcscpy_s( szTemp, Q_MAX_PATH, szFileName );
		_tcscat_s( szTemp, Q_MAX_PATH, _T(".size") );
		#ifdef WIN32
			CRegDWORD dwRead( HKLM, LOG_REGISTRY_KEY, szTemp, nMaxLogFileSize, REG::RF_READONLY );
			m_nMaxLogFileSize = dwRead;
		#else
			char * szVal = getenv( szTemp );
			if( NULL != szVal )
				m_nMaxLogFileSize = atoi( szVal );
		#endif
		m_nMaxLogFileSize = MAKE_LOG_SIZE( m_nMaxLogFileSize );
	}
	else
		m_nMaxLogFileSize = MAKE_LOG_SIZE( nMaxLogFileSize );
	#undef MAKE_LOG_SIZE
}

///
QTrace::LogFile::~LogFile()
{
	m_nMaxLogFileSize = 0;
}

//
bool QTrace::LogFile::Write( const void * const pBuffer, const unsigned long dwBufferSize, unsigned long & dwBytesWrittenRet )
{
	dwBytesWrittenRet = 0;
	if( Q_INVALID( NULL == pBuffer ) || Q_INVALID( 0 == dwBufferSize ) )
		return false;
	if( Q_INVALID( NULL == m_hFile ) )
		return false;
#ifndef WIN32
	struct flock fl;
	memset( &fl, 0, sizeof(fl) );
	fl.l_type = F_WRLCK;
	fl.l_whence = SEEK_END;
	int fid = fileno( m_hFile );
	if( Q_ASSERT( fid >= 0 ) )
	{
		if( Q_ASSERT( -1 != fcntl( fid, F_SETLKW, &fl ) ) )
		{
#endif
			Q_CHECK( 0, fseek( m_hFile, 0, SEEK_END ) );
			dwBytesWrittenRet = fwrite( pBuffer, 1, dwBufferSize, m_hFile ); // and I need it in bytes
			Q_CHECK( dwBufferSize, dwBytesWrittenRet );
			Q_CHECK( 0, fflush( m_hFile ) );
#ifndef WIN32
			memset( &fl, 0, sizeof(fl) );
			fl.l_type = F_UNLCK;
			fl.l_whence = SEEK_END;
			Q_ASSERT( -1 != fcntl( fid, F_SETLK, &fl ) );
		}
	}
#endif
	return (dwBufferSize == dwBytesWrittenRet);
}

/// open a new trace file
bool QTrace::LogFile::OpenTraceDevice()
{
	bool bAlreadyTried = false;
	// Close the trace file if necessary
	if( Q_INVALID( (NULL != m_hFile) ) )
	{
		Q_ASSERT( 0 == fclose( m_hFile ) );
		m_hFile = NULL;
	}
	while( true )
	{
		// Open the trace file
		int nRet = _tfopen_s( &m_hFile, m_szFilename, _T("a+b") );
		if( !Q_CHECK( 0, nRet ) || Q_INVALID( NULL == m_hFile ) )
			return false;
		struct _stat buf;
		nRet = _tstat( m_szFilename, &buf );
		if( bAlreadyTried || ((0 == nRet) && ((m_nMaxLogFileSize / 2) > buf.st_size)) )
			break;
		// if the file is large than the max size, copy it to save and create new
		if( Q_INVALID( 0 != fclose( m_hFile ) ) )
			return false;
		Q_TCHAR szCopyFilename[Q_MAX_PATH] = { 0 };
		_tcscpy_s( szCopyFilename, Q_MAX_PATH, m_szFilename );
		Q_LPTSTR szPos = _tcsrchr( szCopyFilename, _T('.') );
		if( Q_INVALID( NULL == szPos ) )
			return false;
		szPos[0] = 0;
		_tcscat_s( szPos, Q_MAX_PATH, _T(".old.log") );
		_tremove( szCopyFilename ); // ignore the error if the file does not exist
		if( Q_INVALID( 0 != _trename( m_szFilename, szCopyFilename ) ) )
			return false;
		bAlreadyTried = true;
	}
	return true;
}

/// close the current trace file
void QTrace::LogFile::CloseTraceDevice()
{
	if( NULL != m_hFile )
	{
		Q_CHECK( 0, fclose( m_hFile ) );
		m_hFile = NULL;
	}
}

/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!
#endif // #if !defined(DISABLE_Q_TRACE)
/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!

